package com.liyunc.demo.comp.common.utils;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @author liyuncong
 * @version 1.0
 * @file DatesUtils.java
 * @brief DatesUtils
 * @details DatesUtils
 * @date 2022-03-23
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2022-03-23               liyuncong          Created
 */
public class DatesUtils {

    private static final ZoneId ZONEID = ZoneId.systemDefault();

    private static final long WEEK_MAX_CACHE_SIZE = 100L;
    private static final long CACHE_TIMEOUT_HOURS = 2L;
    private static final int MAX_WEEKS = 55;
    private static final int SEVEN = 7;

    public record WeekInfo(int year, int weekNo, LocalDate monday, LocalDate sunday) {
    }

    public record LocaleYear(Locale locale, Integer year) {
    }

    static final LoadingCache<LocaleYear, List<WeekInfo>> WEEK_CACHE = CacheBuilder.newBuilder()
        .expireAfterAccess(CACHE_TIMEOUT_HOURS, TimeUnit.HOURS)
        .maximumSize(WEEK_MAX_CACHE_SIZE)
        .build(new CacheLoader<>() {
            @Override
            public List<WeekInfo> load(LocaleYear localeYear) throws Exception {
                LocalDate firstDay = LocalDate.of(localeYear.year, 1, 1);
                LocalDate monday =
                    firstDay.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
                LocalDate sunday =
                    firstDay.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
                List<WeekInfo> result = new ArrayList<>(MAX_WEEKS);
                int weekNo = 1;
                result.add(new WeekInfo(localeYear.year, weekNo, monday, sunday));
                do {
                    weekNo += 1;
                    monday = monday.plusDays(SEVEN);
                    sunday = monday.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
                    result.add(new WeekInfo(localeYear.year, weekNo, monday, sunday));
                } while (sunday.getYear() <= localeYear.year);
                return result;
            }
        });

    /**
     * LocalDateTime to Date.
     */
    public static Date asDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZONEID).toInstant());
    }

    /**
     * LocalDate to Date.
     */
    public static Date asDate(LocalDate localDate) {
        return Date.from(
            localDate.atStartOfDay(ZONEID).toInstant()
        );
    }

    /**
     * String to Date.
     */
    public static Date asDate(String date) {
        return null;
    }

    /**
     * Date to LocalDateTime.
     */
    public static LocalDateTime asLocalDateTime(Date date) {
        return Instant.ofEpochMilli(date.getTime()).atZone(ZONEID).toLocalDateTime();
    }

    /**
     * LocalDate to LocalDateTime.
     */
    public static LocalDateTime asLocalDateTime(LocalDate localDate) {
        return localDate.atStartOfDay();
    }

    /**
     * String to LocalDateTime.
     */
    public static LocalDateTime asLocalDateTime(String date) {
        return null;
    }

    public static int weekNo() {
        Locale locale = Locale.getDefault();
        LocalDate date = LocalDate.now();
        TemporalField woy = WeekFields.of(locale).weekOfWeekBasedYear();
        return date.get(woy);
    }

    public static int weekNo(final LocalDate date, final Locale locale) {
        TemporalField woy = WeekFields.of(locale).weekOfWeekBasedYear();
        return date.get(woy);
    }

    public static WeekInfo getWeekInfo() throws ExecutionException {
        Locale locale = Locale.getDefault();
        int year = LocalDate.now().getYear();
        int weekNo = weekNo(LocalDate.now(), locale);
        return getWeekInfo(locale, year, weekNo);
    }

    public static WeekInfo getWeekInfo(final Locale locale, final int year, final int weekNo)
        throws ExecutionException {
        LocaleYear localeYear = new LocaleYear(locale, year);
        var weekInfoList = WEEK_CACHE.get(localeYear);
        return weekInfoList.stream()
            .filter(item -> item.year == year && item.weekNo == weekNo)
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Invalid WeekNo"));
    }
}
